import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import seaborn as sns
import numpy as np
from pandastable import Table


class CSVViewerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("CSV数据读取与可视化工具")
        self.root.geometry("1200x700")
        self.root.configure(bg="#2c3e50")

        # 设置应用图标
        try:
            self.root.iconbitmap("data_icon.ico")  # 如果有图标文件可以取消注释
        except:
            pass

        # 初始化变量
        self.df = None
        self.file_path = ""

        # 创建主框架
        self.main_frame = ttk.Frame(root, padding=10)
        self.main_frame.pack(fill=tk.BOTH, expand=True)

        # 创建标题
        title_label = ttk.Label(
            self.main_frame,
            text="CSV数据读取与可视化工具",
            font=("Arial", 20, "bold"),
            foreground="#3498db"
        )
        title_label.pack(pady=10)

        # 创建文件选择区域
        file_frame = ttk.LabelFrame(self.main_frame, text="文件操作", padding=10)
        file_frame.pack(fill=tk.X, padx=10, pady=5)

        # 文件路径显示
        self.file_label = ttk.Label(file_frame, text="未选择文件", width=80, anchor=tk.W)
        self.file_label.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)

        # 文件选择按钮
        browse_btn = ttk.Button(
            file_frame,
            text="选择CSV文件",
            command=self.load_csv,
            style="Accent.TButton"
        )
        browse_btn.pack(side=tk.RIGHT, padx=5)

        # 创建数据显示区域
        data_frame = ttk.LabelFrame(self.main_frame, text="数据预览", padding=10)
        data_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 使用pandastable创建表格
        self.table_frame = ttk.Frame(data_frame)
        self.table_frame.pack(fill=tk.BOTH, expand=True)

        # 创建信息面板
        info_frame = ttk.Frame(self.main_frame)
        info_frame.pack(fill=tk.X, padx=10, pady=5)

        # 行数列数信息
        ttk.Label(info_frame, text="数据维度:").pack(side=tk.LEFT, padx=5)
        self.dim_label = ttk.Label(info_frame, text="0 行 x 0 列")
        self.dim_label.pack(side=tk.LEFT, padx=5)

        # 数据类型信息
        ttk.Label(info_frame, text="数据类型:").pack(side=tk.LEFT, padx=(20, 5))
        self.dtype_label = ttk.Label(info_frame, text="未加载")
        self.dtype_label.pack(side=tk.LEFT, padx=5)

        # 创建可视化区域
        vis_frame = ttk.LabelFrame(self.main_frame, text="数据可视化", padding=10)
        vis_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 可视化控制面板
        control_frame = ttk.Frame(vis_frame)
        control_frame.pack(fill=tk.X, pady=5)

        # 图表类型选择
        ttk.Label(control_frame, text="图表类型:").pack(side=tk.LEFT, padx=5)
        self.chart_type = tk.StringVar(value="line")
        chart_types = [("折线图", "line"), ("柱状图", "bar"), ("散点图", "scatter"),
                       ("直方图", "hist"), ("箱线图", "box"), ("饼图", "pie")]
        for text, mode in chart_types:
            rb = ttk.Radiobutton(control_frame, text=text, variable=self.chart_type, value=mode)
            rb.pack(side=tk.LEFT, padx=5)

        # X轴选择
        ttk.Label(control_frame, text="X轴:").pack(side=tk.LEFT, padx=(20, 5))
        self.x_var = tk.StringVar()
        self.x_combobox = ttk.Combobox(control_frame, textvariable=self.x_var, width=15)
        self.x_combobox.pack(side=tk.LEFT, padx=5)

        # Y轴选择
        ttk.Label(control_frame, text="Y轴:").pack(side=tk.LEFT, padx=(20, 5))
        self.y_var = tk.StringVar()
        self.y_combobox = ttk.Combobox(control_frame, textvariable=self.y_var, width=15)
        self.y_combobox.pack(side=tk.LEFT, padx=5)

        # 可视化按钮
        plot_btn = ttk.Button(
            control_frame,
            text="生成图表",
            command=self.plot_data,
            style="Accent.TButton"
        )
        plot_btn.pack(side=tk.RIGHT, padx=5)

        # 图表显示区域
        self.fig_frame = ttk.Frame(vis_frame)
        self.fig_frame.pack(fill=tk.BOTH, expand=True)
        self.canvas = None

        # 创建状态栏
        self.status_bar = ttk.Label(
            self.root,
            text="就绪",
            relief=tk.SUNKEN,
            anchor=tk.W,
            padding=(5, 2)
        )
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)

        # 配置样式
        self.style = ttk.Style()
        self.style.theme_use("clam")
        self.style.configure(".", background="#ecf0f1", foreground="#2c3e50")
        self.style.configure("TLabel", background="#ecf0f1")
        self.style.configure("TFrame", background="#ecf0f1")
        self.style.configure("TButton", background="#3498db", foreground="white", borderwidth=1)
        self.style.configure("Accent.TButton", background="#e74c3c", foreground="white")
        self.style.configure("TLabelframe", background="#ecf0f1", borderwidth=2)
        self.style.configure("TLabelframe.Label", background="#ecf0f1", foreground="#2c3e50")
        self.style.configure("TCombobox", fieldbackground="white")
        self.style.map("TButton", background=[("active", "#2980b9")])
        self.style.map("Accent.TButton", background=[("active", "#c0392b")])

        # 添加一些示例数据提示
        self.show_sample_data_hint()

    def show_sample_data_hint(self):
        hint_frame = ttk.Frame(self.main_frame)
        hint_frame.pack(fill=tk.X, padx=10, pady=5)

        hint_text = "提示: 如果没有CSV文件，可以使用以下示例数据：\n"
        hint_text += "日期,产品,销售额,数量,利润\n"
        hint_text += "2023-01-01,A,1200,24,300\n"
        hint_text += "2023-01-02,B,800,16,200\n"
        hint_text += "2023-01-03,A,1500,30,375\n"
        hint_text += "2023-01-04,C,950,19,240\n"
        hint_text += "2023-01-05,B,1100,22,275"

        hint_label = ttk.Label(
            hint_frame,
            text=hint_text,
            justify=tk.LEFT,
            font=("Consolas", 9),
            foreground="#7f8c8d"
        )
        hint_label.pack(anchor=tk.W)

    def load_csv(self):
        file_path = filedialog.askopenfilename(
            filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")]
        )
        if not file_path:
            return

        try:
            self.file_path = file_path
            self.file_label.config(text=file_path)
            self.status_bar.config(text=f"正在加载文件: {file_path}")
            self.root.update()

            # 读取CSV文件
            self.df = pd.read_csv(file_path)

            # 更新数据信息
            self.dim_label.config(text=f"{self.df.shape[0]} 行 x {self.df.shape[1]} 列")

            # 显示数据类型
            dtypes = ", ".join([f"{col}: {dtype}" for col, dtype in zip(self.df.columns, self.df.dtypes)])
            self.dtype_label.config(text=dtypes[:80] + "..." if len(dtypes) > 80 else dtypes)

            # 更新列选择
            self.x_combobox["values"] = self.df.columns.tolist()
            self.y_combobox["values"] = self.df.columns.tolist()

            if len(self.df.columns) > 0:
                self.x_var.set(self.df.columns[0])
                if len(self.df.columns) > 1:
                    self.y_var.set(self.df.columns[1])

            # 显示表格数据
            self.show_table()

            self.status_bar.config(text=f"成功加载文件: {file_path}")
        except Exception as e:
            messagebox.showerror("错误", f"无法加载CSV文件:\n{str(e)}")
            self.status_bar.config(text="文件加载失败")

    def show_table(self):
        # 清除旧表格
        for widget in self.table_frame.winfo_children():
            widget.destroy()

        # 创建新表格
        if self.df is not None:
            # 使用pandastable显示表格
            pt = Table(
                self.table_frame,
                dataframe=self.df.head(100),  # 只显示前100行
                showtoolbar=True,
                showstatusbar=True
            )
            pt.show()

    def plot_data(self):
        if self.df is None:
            messagebox.showwarning("警告", "请先加载CSV文件")
            return

        # 获取选择的列
        x_col = self.x_var.get()
        y_col = self.y_var.get()
        chart_type = self.chart_type.get()

        if not x_col:
            messagebox.showwarning("警告", "请选择X轴列")
            return

        # 清除旧图表
        for widget in self.fig_frame.winfo_children():
            widget.destroy()

        try:
            self.status_bar.config(text="正在生成图表...")
            self.root.update()

            # 创建图表
            fig, ax = plt.subplots(figsize=(8, 4), dpi=100)
            fig.patch.set_facecolor('#ecf0f1')
            ax.set_facecolor('#ecf0f1')

            # 根据选择的图表类型绘图
            if chart_type == "line":
                if y_col:
                    # 如果有多列，绘制多条线
                    if isinstance(y_col, list):
                        for col in y_col:
                            ax.plot(self.df[x_col], self.df[col], label=col)
                        ax.legend()
                    else:
                        ax.plot(self.df[x_col], self.df[y_col])
                else:
                    # 如果没有选择Y轴，尝试绘制所有数值列
                    numeric_cols = self.df.select_dtypes(include=[np.number]).columns.tolist()
                    if len(numeric_cols) > 0:
                        for col in numeric_cols:
                            ax.plot(self.df[x_col], self.df[col], label=col)
                        ax.legend()
                    else:
                        messagebox.showwarning("警告", "没有找到数值列")
                        return

                ax.set_title(f"{x_col} vs {y_col if y_col else '数值列'}")
                ax.set_xlabel(x_col)
                ax.set_ylabel(y_col if y_col else "数值")

            elif chart_type == "bar":
                if y_col:
                    ax.bar(self.df[x_col], self.df[y_col])
                    ax.set_title(f"{x_col} vs {y_col}")
                    ax.set_ylabel(y_col)
                else:
                    # 如果没有选择Y轴，显示每个类别的计数
                    counts = self.df[x_col].value_counts()
                    ax.bar(counts.index, counts.values)
                    ax.set_title(f"{x_col} 分布")
                    ax.set_ylabel("计数")
                ax.set_xlabel(x_col)

            elif chart_type == "scatter":
                if not y_col:
                    messagebox.showwarning("警告", "散点图需要选择X轴和Y轴")
                    return
                ax.scatter(self.df[x_col], self.df[y_col])
                ax.set_title(f"{x_col} vs {y_col}")
                ax.set_xlabel(x_col)
                ax.set_ylabel(y_col)

            elif chart_type == "hist":
                if not y_col or self.df[y_col].dtype.kind not in 'biufc':
                    # 如果没有选择Y轴或Y轴不是数值，使用X轴
                    col = y_col if y_col and self.df[y_col].dtype.kind in 'biufc' else x_col
                    ax.hist(self.df[col].dropna(), bins=20, color='skyblue', edgecolor='black')
                    ax.set_title(f"{col} 分布")
                    ax.set_xlabel(col)
                    ax.set_ylabel("频率")
                else:
                    ax.hist(self.df[y_col].dropna(), bins=20, color='skyblue', edgecolor='black')
                    ax.set_title(f"{y_col} 分布")
                    ax.set_xlabel(y_col)
                    ax.set_ylabel("频率")

            elif chart_type == "box":
                if y_col:
                    # 分组箱线图
                    if self.df[x_col].nunique() < 10:  # 如果类别不多
                        sns.boxplot(x=x_col, y=y_col, data=self.df, ax=ax)
                        ax.set_title(f"{x_col} 分组下的 {y_col} 分布")
                    else:
                        ax.boxplot(self.df[y_col])
                        ax.set_title(f"{y_col} 分布")
                        ax.set_ylabel(y_col)
                else:
                    # 单变量箱线图
                    ax.boxplot(self.df[x_col].dropna())
                    ax.set_title(f"{x_col} 分布")
                    ax.set_ylabel(x_col)

            elif chart_type == "pie":
                if not y_col:
                    # 如果没有选择Y轴，显示每个类别的计数
                    counts = self.df[x_col].value_counts()
                    ax.pie(counts.values, labels=counts.index, autopct='%1.1f%%', startangle=90)
                    ax.set_title(f"{x_col} 分布")
                else:
                    # 如果有Y轴，使用Y轴的值
                    # 分组求和
                    grouped = self.df.groupby(x_col)[y_col].sum()
                    ax.pie(grouped.values, labels=grouped.index, autopct='%1.1f%%', startangle=90)
                    ax.set_title(f"{x_col} 的 {y_col} 占比")

            # 美化图表
            plt.tight_layout()

            # 在Tkinter中显示图表
            self.canvas = FigureCanvasTkAgg(fig, master=self.fig_frame)
            self.canvas.draw()
            self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

            self.status_bar.config(text="图表生成成功")
        except Exception as e:
            messagebox.showerror("错误", f"生成图表时出错:\n{str(e)}")
            self.status_bar.config(text="图表生成失败")


# 创建主窗口
if __name__ == "__main__":
    root = tk.Tk()
    app = CSVViewerApp(root)
    root.mainloop()